package com.mapforge.map.tools.selection;

import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JOptionPane;

import com.mapforge.InputMapping;
import com.mapforge.exceptions.ChosenTileLayerException;
import com.mapforge.exceptions.EmptySelectionException;
import com.mapforge.exceptions.TileLayerStateException;
import com.mapforge.gui.interfaces.TileMapEditorGUIInterface;
import com.mapforge.listener.MapToolChangeEvent;
import com.mapforge.listener.MapToolChangeListener;
import com.mapforge.listener.TileMapCreationEvent;
import com.mapforge.listener.TileMapCreationListener;
import com.mapforge.map.TileMap;
import com.mapforge.map.tools.MapToolInterface;

/**
 * The {@link SelectionMouseAdapter} class handles informing map tools about
 * {@link MouseEvent mouse events} occurring on
 * {@link TileMapEditorGUIInterface tile map GUI components}
 * 
 * @author Will Morrison
 * 
 */
public class SelectionMouseAdapter extends MouseAdapter implements
		MapToolChangeListener, TileMapCreationListener {
	private MapToolInterface mapTool;
	private Point startingPoint;
	private Point currentPoint;
	private TileMap tileMap;

	public SelectionMouseAdapter() {
	}

	public void setSelectionTool(AbstractSelectionTool selectionTool) {
		this.mapTool = selectionTool;
	}

	public MapToolInterface getSelectionTool() {
		return mapTool;
	}

	public Point getStartPoint() {
		return startingPoint;
	}

	public Point getCurrentPoint() {
		return currentPoint;
	}

	/**
	 * Returns the current tile relative to some point. Equivalent to calling
	 * tileAt(p.x,p.y)
	 * 
	 * @param p
	 *            some point
	 * @return a point location of the current tile
	 */
	public Point tileAt(Point p) {
		return tileAt(p.x, p.y);
	}

	/**
	 * Returns the current tile x and y locations relative to an x and y
	 * location.
	 * 
	 * @param x
	 *            x location
	 * @param y
	 *            y location
	 * @return the x and y location of the current tile
	 */
	public Point tileAt(double x, double y) {
		if (tileMap != null) {
			Point tileAt = new Point();
			int w = tileMap.getWidth();
			int h = tileMap.getHeight();
			if (x < tileMap.getX() || x > tileMap.getX() + w
					|| y < tileMap.getY() || y > tileMap.getY() + h) {
				return null;
			}
			x += tileMap.getX();
			y += tileMap.getY();
			tileAt.x = (int) (x / tileMap.getTileWidth());
			tileAt.y = (int) (y / tileMap.getTileHeight());
			return tileAt;
		} else {
			return null;
		}
	}

	public void mousePressed(MouseEvent e) {
		if (e.getButton() != InputMapping.POPUP_TRIGGER) {
			if (this.startingPoint == null) {
				Point p = tileAt(e.getPoint());
				if (p != null) {
					this.startingPoint = p;
					this.currentPoint = new Point(p);
					this.mapTool.setAnchorPoint(startingPoint);
					this.mapTool.setMovingPoint(currentPoint);
					// beginning edit, throws exception if in illegal state
					try {
						this.mapTool.beginEdit();
					} catch (ChosenTileLayerException e2) {
						showExceptionDialog(e2);
					} catch (EmptySelectionException e2) {
						showExceptionDialog(e2);
					} catch (TileLayerStateException e2) {
						showExceptionDialog(e2);
					}
				}
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if (e.getButton() != InputMapping.POPUP_TRIGGER
				&& startingPoint != null && currentPoint != null) {
			this.mapTool.setAnchorPoint(startingPoint);
			this.mapTool.setMovingPoint(currentPoint);
			// end edit, mouse was released at this point
			try {
				this.mapTool.endEdit();
			} catch (ChosenTileLayerException e2) {
				showExceptionDialog(e2);
			} catch (EmptySelectionException e2) {
				showExceptionDialog(e2);
			} catch (TileLayerStateException e2) {
				showExceptionDialog(e2);
			}
		}
		this.startingPoint = null;
	}

	private void showExceptionDialog(Exception e) {
		JOptionPane.showMessageDialog(null, "Unable to perform action.\n"
				+ e.getMessage());
		e.printStackTrace();
		try {
			startingPoint = null;
			if (mapTool.isEditing()) {
				mapTool.endEdit();
			}
		} catch (ChosenTileLayerException e2) {
		} catch (EmptySelectionException e2) {
		} catch (TileLayerStateException e2) {
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		if (startingPoint != null) {
			Point p = tileAt(e.getPoint());
			if (p != null) {
				this.currentPoint = p;
				Point movingPoint = mapTool.getMovingPoint();
				this.mapTool.setMovingPoint(this.currentPoint);
				// point was different from last know point, perform edit
				if (movingPoint.x != p.x || movingPoint.y != p.y) {
					try {
						mapTool.performEdit();
					} catch (ChosenTileLayerException e2) {
						showExceptionDialog(e2);
					} catch (EmptySelectionException e2) {
						showExceptionDialog(e2);
					} catch (TileLayerStateException e2) {
						showExceptionDialog(e2);
					}
				}
			}
		}
	}

	public void tileMapCreationPerformed(TileMapCreationEvent mapCreationEvent) {
		this.tileMap = mapCreationEvent.getTileMapCreated();
		this.mapTool.setTileMap(this.tileMap);
	}

	@Override
	public void mapToolChangePerformed(MapToolChangeEvent e) {
		this.mapTool = e.getMapTool();
	}
}